import threading,subprocess,time
from pathlib import Path
from datetime import datetime
from typing import Any,Self
import ramda as R
import game,logger

class Const:
    TIME_SECOND_MAX = 2147483648
    LOC_TO_TEXT = [
        "A15","B15","C15","D15","E15","F15","G15","H15","I15","J15","K15","L15","M15","N15","O15",
        "A14","B14","C14","D14","E14","F14","G14","H14","I14","J14","K14","L14","M14","N14","O14",
        "A13","B13","C13","D13","E13","F13","G13","H13","I13","J13","K13","L13","M13","N13","O13",
        "A12","B12","C12","D12","E12","F12","G12","H12","I12","J12","K12","L12","M12","N12","O12",
        "A11","B11","C11","D11","E11","F11","G11","H11","I11","J11","K11","L11","M11","N11","O11",
        "A10","B10","C10","D10","E10","F10","G10","H10","I10","J10","K10","L10","M10","N10","O10",
        "A9", "B9", "C9", "D9", "E9", "F9", "G9", "H9", "I9", "J9", "K9", "L9", "M9", "N9", "O9",
        "A8", "B8", "C8", "D8", "E8", "F8", "G8", "H8", "I8", "J8", "K8", "L8", "M8", "N8", "O8",
        "A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7", "I7", "J7", "K7", "L7", "M7", "N7", "O7",
        "A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6", "I6", "J6", "K6", "L6", "M6", "N6", "O6",
        "A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5", "I5", "J5", "K5", "L5", "M5", "N5", "O5",
        "A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4", "I4", "J4", "K4", "L4", "M4", "N4", "O4",
        "A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3", "I3", "J3", "K3", "L3", "M3", "N3", "O3",
        "A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2", "I2", "J2", "K2", "L2", "M2", "N2", "O2",
        "A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1", "I1", "J1", "K1", "L1", "M1", "N1", "O1",
    ]
    TEXT_TO_LOC = {
        "A15":0,"B15":1,"C15":2,"D15":3,"E15":4,"F15":5,"G15":6,"H15":7,"I15":8,"J15":9,"K15":10,"L15":11,"M15":12,"N15":13,"O15":14,
        "A14":15,"B14":16,"C14":17,"D14":18,"E14":19,"F14":20,"G14":21,"H14":22,"I14":23,"J14":24,"K14":25,"L14":26,"M14":27,"N14":28,"O14":29,
        "A13":30,"B13":31,"C13":32,"D13":33,"E13":34,"F13":35,"G13":36,"H13":37,"I13":38,"J13":39,"K13":40,"L13":41,"M13":42,"N13":43,"O13":44,
        "A12":45,"B12":46,"C12":47,"D12":48,"E12":49,"F12":50,"G12":51,"H12":52,"I12":53,"J12":54,"K12":55,"L12":56,"M12":57,"N12":58,"O12":59,
        "A11":60,"B11":61,"C11":62,"D11":63,"E11":64,"F11":65,"G11":66,"H11":67,"I11":68,"J11":69,"K11":70,"L11":71,"M11":72,"N11":73,"O11":74,
        "A10":75,"B10":76,"C10":77,"D10":78,"E10":79,"F10":80,"G10":81,"H10":82,"I10":83,"J10":84,"K10":85,"L10":86,"M10":87,"N10":88,"O10":89,
        "A9":90,"B9":91,"C9":92,"D9":93,"E9":94,"F9":95,"G9":96,"H9":97,"I9":98,"J9":99,"K9":100,"L9":101,"M9":102,"N9":103,"O9":104,
        "A8":105,"B8":106,"C8":107,"D8":108,"E8":109,"F8":110,"G8":111,"H8":112,"I8":113,"J8":114,"K8":115,"L8":116,"M8":117,"N8":118,"O8":119,
        "A7":120,"B7":121,"C7":122,"D7":123,"E7":124,"F7":125,"G7":126,"H7":127,"I7":128,"J7":129,"K7":130,"L7":131,"M7":132,"N7":133,"O7":134,
        "A6":135,"B6":136,"C6":137,"D6":138,"E6":139,"F6":140,"G6":141,"H6":142,"I6":143,"J6":144,"K6":145,"L6":146,"M6":147,"N6":148,"O6":149,
        "A5":150,"B5":151,"C5":152,"D5":153,"E5":154,"F5":155,"G5":156,"H5":157,"I5":158,"J5":159,"K5":160,"L5":161,"M5":162,"N5":163,"O5":164,
        "A4":165,"B4":166,"C4":167,"D4":168,"E4":169,"F4":170,"G4":171,"H4":172,"I4":173,"J4":174,"K4":175,"L4":176,"M4":177,"N4":178,"O4":179,
        "A3":180,"B3":181,"C3":182,"D3":183,"E3":184,"F3":185,"G3":186,"H3":187,"I3":188,"J3":189,"K3":190,"L3":191,"M3":192,"N3":193,"O3":194,
        "A2":195,"B2":196,"C2":197,"D2":198,"E2":199,"F2":200,"G2":201,"H2":202,"I2":203,"J2":204,"K2":205,"L2":206,"M2":207,"N2":208,"O2":209,
        "A1":210,"B1":211,"C1":212,"D1":213,"E1":214,"F1":215,"G1":216,"H1":217,"I1":218,"J1":219,"K1":220,"L1":221,"M1":222,"N1":223,"O1":224,
    }
    #
    PIPE_LINE_MAXSIZE = 2048
    HEARTBEAT_TAKE_TIMEOUT = 6.0
    HEARTBEAT_SEND_TIMEOUT = 4.5
    SESSION_WAIT_READY_TIMEOUT = 3.0
    SESSION_STOP_TIMEOUT = 1.0
    SYSTEM_THREAD_STOP_TIMEOUT = 1.0
    SYSTEM_THREAD_POLL_SPAC_TIME = 0.05

# Common Fromat by Ux & Enging
class BOTH:
    HEAD_HEARTBEAT = "HEARTBEAT"
    SPLIT_0 = ' '
    SPLIT_1 = '|'
    SPLIT_2 = ','

    CONFITEM_INT = 1
    CONFITEM_FLOAT = 2
    CONFITEM_BOOL = 3
    CONFITEM_TEXT = 4
    class ConfItem:
        def __init__(me, id:str, type_id:int, _min=None, _max=None, _step=None) -> None:
            me.__id = id
            me.__type_id = type_id
            me.__min = _min
            me.__max = _max
            me.__step = _step
            me.__val = None
        def id(me) -> str: return me.__id
        def min(me) -> Any: return me.__min
        def max(me) -> Any: return me.__max
        def step(me) -> Any: return me.__step
        def type_id(me) -> int: return me.__type_id
        def is_int(me) -> bool: return (me.__type_id == BOTH.CONFITEM_INT)
        def is_float(me) -> bool: return (me.__type_id == BOTH.CONFITEM_FLOAT)
        def is_number(me) -> bool: return (me.is_int() or me.is_float())
        def is_bool(me) -> bool: return (me.__type_id == BOTH.CONFITEM_BOOL)
        def is_text(me) -> bool: return (me.__type_id == BOTH.CONFITEM_TEXT)
        def value(me) -> Any: return me.__val
        def value_to(me, val:Any) -> Self:
            if me.is_int():
                val = R.try_catch(int, R.always(me.__min))(val)
                me.__val = min(max(me.__min, val), me.__max)
            elif me.is_float():
                val = R.try_catch(float, R.always(me.__max))(val)
                me.__val = min(max(me.__min, val), me.__max)
            elif me.is_bool():
                if isinstance(val,str):
                    val = (True if (val.lower() == "yes") else False)
                me.__val = val
            else:
                me.__val = val
            return me

# Base Format by Ux -> Engine
class UE:
    HEAD_GET = "GET"
    HEAD_SET_BOARD = "SET-BOARD"
    HEAD_SET_RULE = "SET-RULE"
    HEAD_SET_CONF = "SET-CONF"
    HEAD_RESET = "RESET"
    HEAD_RAII = "RAII"

    class Piece:
        def __init__(me, p:int) -> None:
            me.__text = (['b','','w'][1+p])
        def __str__(me) -> str:
            return me.__text

    class Point:
        def __init__(me, h:int, v:int) -> None:
            me.__text =  ""
            if (h in range(game.BOARD_SIZE)) and (v in range(game.BOARD_SIZE)):
                loc = game.hv_to_loc(h, v)
                me.__text = Const.LOC_TO_TEXT[loc]
        def __str__(me) -> str:
            return me.__text

    class PointString:
        def __init__(me, points:list) -> None:
            me.__points = points.copy()
            me.__text = ""
            if len(me.__points) > 1:
                def f(sum:str, now):
                    return f"{sum}{BOTH.SPLIT_2}{now}"
                me.__text = R.reduce(f, me.__points[0], me.__points[1:])
            elif len(me.__points) == 1:
                me.__text = str(me.__points[0])
        def __str__(me) -> str:
            return me.__text

    class TimeSecond:
        def __init__(me, second:int) -> None:
            me.__text = str(min(max(second, 2), Const.TIME_SECOND_MAX))
        def __str__(me) -> str:
            return me.__text

    class Rule:
        def __init__(me, text:str) -> None:
            me.__text = text
        def __str__(me) -> str:
            return me.__text
    class Rule_forbidden(Rule):
        def __init__(me, use_forbidden:bool) -> None:
            tail = "yes" if use_forbidden else "no"
            super().__init__(f"forbidden {tail}")
    class Rule_canswaps(Rule):
        def __init__(me, steps:tuple) -> None:
            tail = R.join(BOTH.SPLIT_1, steps)
            super().__init__(f"canswaps {tail}")

class Encode:
    def Get(engine_piece:int, second_usable:int, id:str) -> str:
        P = UE.Piece(engine_piece)
        T = UE.TimeSecond(second_usable)
        return f"{UE.HEAD_GET} {P} {T} {id}"

    def SetBoard(board:game.Board, last:game.MoveStep) -> str:
        Bs = []
        Ws = []
        def f(h:int, v:int, p:int):
            if p == game.P_BLACK: Bs.append(UE.Point(h,v))
            elif p == game.P_WHITE: Ws.append(UE.Point(h,v))
        board.for_hv(f)
        Last = ""
        if isinstance(last, game.MoveStep):
            h,v = game.loc_to_hv(last.loc())
            Last = str(UE.Point(h,v))
        return f"{UE.HEAD_SET_BOARD} {UE.PointString(Bs)}{BOTH.SPLIT_1}{UE.PointString(Ws)}{BOTH.SPLIT_1}{Last}"

    def SetRule(rule:UE.Rule) -> str:
        return f"{UE.HEAD_SET_RULE} {rule}"

    def SetConf(item:BOTH.ConfItem) -> str:
        val_show = item.value()
        if item.is_bool():
            val_show = ("yes" if val_show else "no")
        else:
            val_show = str(val_show)
        return f"{UE.HEAD_SET_CONF} {item.id} {val_show}"

# Base Format by Engine -> Ux
class EU:
    HEAD_MOVE = "MOVE"
    HEAD_RELPY_GIVE_POINTS = "R-GIVE-POINTS"
    HEAD_REPLY_SELECT_POINT = "R-SELECT-POINT"
    HEAD_IN_POINTS = "IN-POINTS"
    HEAD_STATUS = "STATUS"
    HEAD_SAY = "SAY"
    HEAD_CONF = "CONF"
    HEAD_CRASH = "CRASH"
    HEAD_READY = "READY"

    class Point:
        def __init__(me, raw:str) -> None:
            loc = Const.TEXT_TO_LOC.get(raw.upper())
            if isinstance(loc,int):
                me.__h,me.__v = game.loc_to_hv(loc)
            else:
                me.__h = 0
                me.__v = 0
        def h(me) -> int: return me.__h
        def v(me) -> int: return me.__v

    class PointString:
        def __init__(me, raw:str) -> None:
            me.__points = R.map(EU.Point, R.filter(R.complement(R.empty), raw.split(BOTH.SPLIT_2)))
        def points(me) -> list: return me.__points

    class Move:
        def __init__(me, raw:str) -> None:
            text = raw.upper()
            if "SWAP" == text:
                me.__type = 1
            elif "PASS" == text:
                me.__type = 2
            else:
                me.__type = 0
                me.__point = EU.Point(raw)
        def point(me): return me.__point
        def is_point(me) -> bool: return (0 == me.__type)
        def is_swap(me) -> bool: return (1 == me.__type)
        def is_pass(me) -> bool: return (2 == me.__type)

    class TextLine:
        def __init__(me, raw:str) -> None:
            me.__text = raw
        def __str__(me) -> str:
            return me.__text

    class TextBlock:
        def __init__(me, raw:str) -> None:
            me.__text = raw
        def __str__(me) -> str:
            return me.__text

    class ConfType:
        def __init__(me, raw:str) -> None:
            _type_id = None
            _min = None
            _max = None
            _step = None
            units = raw.split(BOTH.SPLIT_1)
            if (len(units) > 0) and (len(units[0]) > 0):
                try:
                    if (units[0] == 'i') and (len(units) > 2):
                        _type_id = BOTH.CONFITEM_INT
                        _min = int(units[1])
                        _max = int(units[2])
                        _step = int(units[3]) if (len(units) > 3) else 1
                    elif units[0] == 'f':
                        _type_id = BOTH.CONFITEM_FLOAT
                        _min = float(units[1])
                        _max = float(units[2])
                        _step = float(units[3]) if (len(units) > 3) else 1.0
                    elif units[0] == 'b':
                        _type_id = BOTH.CONFITEM_BOOL
                    elif units[0] == 's':
                        _type_id = BOTH.CONFITEM_TEXT
                except:
                    _min = None
                    _max = None
                    _step = None
            me.__t = (_type_id,_min,_max,_step)
        def type_args(me) -> tuple: return me.__t

class Decode:
    def Move(units:list) -> tuple:
        if len(units) > 1:
            _id = units[0]
            _move = EU.Move(units[1])
            return (_id, _move)
        return (None, None)

    def ReplyGivePoints(units:list):
        if len(units) > 1:
            _id = units[0]
            _locs = R.map(EU.Point, R.filter(R.complement(R.empty), units[1].split(BOTH.SPLIT_1)))
            return (_id, _locs)
        return (None,)

    def ReplySelectPoint(units:list):
        if len(units) > 1:
            _id = units[0]
            _loc = EU.Point(units[1])
            return (_id, _loc)
        return (None,)

    def InPoints(units:list) -> tuple:
        if len(units) > 0:
            levels = R.map(EU.PointString, R.filter(R.complement(R.empty), units[0].split(BOTH.SPLIT_1)))
            return (levels,)
        return (None,)

    def Status(units:list) -> tuple:
        if len(units) > 0:
            return (EU.TextLine(units[0]),)
        return (None,)

    def Say(units:list) -> tuple:
        if len(units) > 0:
            return (EU.TextBlock(units[0]),)
        return (None,)

    def Conf(units:list) -> tuple:
        if len(units) > 2:
            _id = units[0]
            _type_args = EU.ConfType(units[1]).type_args()
            _default = units[2]
            _show_name = units[3] if (len(units) > 3) else _id
            return (BOTH.ConfItem(_id, *_type_args).value_to(_default), _show_name)
        return (None,)

    def Crash(units:list) -> tuple:
        if len(units) > 0:
            return (EU.TextBlock(units[0]),)
        return (None,)

class Receiver:
    def __init__(me) -> None:
        me.__routes = {
            EU.HEAD_MOVE:(Decode.Move, me.Move),
            EU.HEAD_RELPY_GIVE_POINTS:(Decode.ReplyGivePoints, me.ReplyGivePoints),
            EU.HEAD_REPLY_SELECT_POINT:(Decode.ReplySelectPoint, me.ReplySelectPoint),
            EU.HEAD_IN_POINTS:(Decode.InPoints, me.InPoints),
            EU.HEAD_STATUS:(Decode.Status, me.Status),
            EU.HEAD_SAY:(Decode.Say, me.Say),
            EU.HEAD_CONF:(Decode.Conf, me.Conf),
            EU.HEAD_CRASH:(Decode.Crash, me.Crash),
        }
    def __call__(me, line:str, conn) -> None:
        units = R.filter(R.complement(R.empty), line.strip().split(' '))
        if len(units) > 0:
            head = units[0].upper()
            route = me.__routes.get(head)
            if route is None:
                me._Other(conn, line)
            else:
                route[1](conn, *(route[0](units[1:])))
        return None
    #
    def Move(me, *_) -> None: pass
    def ReplyGivePoints(me, *_) -> None: pass
    def ReplySelectPoint(me, *_) -> None: pass
    def InPoints(me, *_) -> None: pass
    def Status(me, *_) -> None: pass
    def Say(me, *_) -> None: pass
    def Conf(me, *_) -> None: pass
    def Crash(me, *_) -> None: pass
    def _Other(me, *_) -> None: pass
    #
    def protocol_on_connected(me, *_) -> None: pass
    def protocol_on_connect_fail(me, *_) -> None: pass
    def protocol_on_connect_not_usable(me, *_) -> None: pass

class Session(threading.Thread):
    POPEN_KWARGS = {
        "startupinfo":subprocess.STARTUPINFO(
            dwFlags = subprocess.STARTF_USESHOWWINDOW | subprocess.HIGH_PRIORITY_CLASS,
            wShowWindow = subprocess.SW_HIDE,
        ),
        "stdin":subprocess.PIPE,
        "stdout":subprocess.PIPE,
        "stderr":subprocess.PIPE,
        "universal_newlines":True,
        "bufsize": 1,
    }
    def __init__(me, conn, open_args:list, receiver:Receiver) -> None:
        threading.Thread.__init__(me)
        me.__conn = conn
        me.__process = subprocess.Popen(open_args, **(Session.POPEN_KWARGS))
        me.__receiver = receiver
        me.__next = False
        me.__heartbeat_take_time = datetime.now()
        me.setDaemon(True)

    def is_next(me) -> bool: return me.__next

    def heartbeat_take_time(me) -> datetime: return me.__heartbeat_take_time

    def __parse_ready(me, line:str):
        units = line.strip().split(' ')
        if len(units) > 0:
            if units[0].upper() == EU.HEAD_READY:
                if len(units) > 1:
                    return units[1]
                else:
                    return ""
        return None

    def __wait_ready(me) -> bool:
        logger.info("protocol.Session", "wait ready")
        if (me.__process.poll() is None) and me.__process.stdout.readable():
            line =  me.__process.stdout.readline(Const.PIPE_LINE_MAXSIZE)
            engine_name = me.__parse_ready(line)
            if isinstance(engine_name, str):
                logger.info("protocol.Session", f"engine({engine_name}) has ready")
                me.__conn._session_has_ready(engine_name, me)
                return True
        return False

    def run(me):
        logger.info("protocal.Session", f"run by '{me.__conn.path()}'")
        try:
            if me.__wait_ready():
                me.__next = True
                while (me.__process.poll() is None) and me.__next:
                    if me.__process.stdout.readable():
                        outs = me.__process.stdout.readline(Const.PIPE_LINE_MAXSIZE)
                        take = outs.strip()
                        if len(take) > 0:
                            logger.info("protocol.Session", f"take: {take}")
                            if (BOTH.HEAD_HEARTBEAT == take.upper()):
                                me.__heartbeat_take_time = datetime.now()
                            else:
                                me.__receiver(take, me.__conn)
                me.__next = False
                me.__process.wait(timeout=Const.SESSION_STOP_TIMEOUT)
                me.__process.terminate()
        except Exception as e:
            print(str(e))
            logger.write_traceback_now()
        finally:
            me.__conn._session_has_stop()
            logger.info("protocal.Session", "has stop")

    def stop(me) -> None:
        if me.__next:
            me.send(UE.HEAD_RAII)
            me.__next = False

    def send(me, text:str) -> None:
        try:
            logger.info("protocal.Session", f"send: {text}")
            me.__process.stdin.write(f"{text}\n")
            me.__process.stdin.flush()
        except Exception as e:
            print(str(e))
            logger.write_traceback_now()

class Connection:
    def __init__(me, path:Path, id:int) -> None:
        me.__path = path
        me.__id = id
        me.__name = path.name
        me.__session = None

    def path(me) -> Path: return me.__path
    def id(me) -> int: return me.__id
    def name(me) -> str: return me.__name

    def useable(me) -> None:
        return (isinstance(me.__session, Session) and me.__session.is_next())

    def send(me, text:str) -> None:
        assert isinstance(me.__session, Session) and me.__session.is_next()
        me.__session.send(text)

    def _session_has_ready(me, engine_name:str, session:Session) -> None:
        me.__name = engine_name
        me.__session = session

    def _session_has_stop(me) -> None:
        me.__session = None

class _SystemItem:
    def __init__(me, conn:Connection, session:Session) -> None:
        me.conn = conn
        me.session = session
        me.init_time = datetime.now()
        me.heartbeat_send_time = None
class _SystemThread(threading.Thread):
    def __init__(me, receiver:Receiver) -> None:
        threading.Thread.__init__(me)
        me.__receiver = receiver
        me.__items = []
        me.__next = True
        me.setDaemon(True)

    def n_usable(me) -> int:
        return len(me.__items)

    def add(me, conn:Connection, session:Session) -> None:
        assert not session.is_alive()
        session.start()
        me.__items.append(_SystemItem(conn, session))

    def item_at(me, i:int) -> _SystemItem:
        assert i in range(len(me.__items))
        return me.__items[i]

    def for_items(me, f) -> None:
        for item in me.__items:
            if item.session.is_alive():
                f(item.conn, item.session)

    def stop(me) -> None:
        me.__next = False

    def run(me):
        logger.info("protocol._SystemThread", "run")
        try:
            while me.__next:
                item_usables = []
                for item in me.__items:
                    # Step-1 Check READY
                    if isinstance(item.init_time, datetime):
                        if item.session.is_next():
                            item.heartbeat_send_time = item.init_time
                            item.init_time = None
                            me.__receiver.protocol_on_connected(item.conn)
                        elif (datetime.now() - item.init_time).total_seconds() > Const.SESSION_WAIT_READY_TIMEOUT:
                            me.__receiver.protocol_on_connect_fail(item.conn.path())
                            continue    # !!!!!!!![Drop]
                    # Step-2 Check Session Live
                    elif not (item.session.is_alive() and item.session.is_next()):
                        me.__receiver.protocol_on_connect_not_usable(item.conn)
                        continue        # !!!!!!!![Drop]
                    # Step-3 Check HEARTBEAT by Take
                    elif (datetime.now() - item.session.heartbeat_take_time()).total_seconds() > Const.HEARTBEAT_TAKE_TIMEOUT:
                        item.session.stop()
                        me.__receiver.protocol_on_connect_not_usable(item.conn)
                        continue        # !!!!!!!![Drop]
                    # Step-4 Check HEARTBEAT by Send
                    elif (datetime.now() - item.heartbeat_send_time).total_seconds() > Const.HEARTBEAT_SEND_TIMEOUT:
                        item.heartbeat_send_time = datetime.now()
                        item.session.send(BOTH.HEAD_HEARTBEAT)
                    # Step-5
                    item_usables.append(item)
                me.__items = item_usables
                time.sleep(Const.SYSTEM_THREAD_POLL_SPAC_TIME)
            #
            for item in me.__items:
                item.session.stop()
                item.session.join(Const.SESSION_STOP_TIMEOUT)
        except Exception as e:
            print(str(e))
            logger.write_traceback_now()
        finally:
            me.__next = False
            logger.info("protocol._SystemThread", "has stop")

class System:
    def __init__(me, receiver:Receiver) -> None:
        me.__receiver = receiver
        me.__thread = _SystemThread(receiver)
        me.__thread.start()
        me.__all = []

    def raii(me) -> None:
        logger.info("protocal.System", "raii")
        me.__thread.stop()
        me.__thread.join(Const.SYSTEM_THREAD_STOP_TIMEOUT)
        me.__thread = None
        logger.info("protocal.System", "has raii")

    def connect(me, path:Path) -> int:
        assert isinstance(me.__thread, threading.Thread) and me.__thread.is_alive()
        logger.info("protocal.System", f"connect '{path}'")
        id = len(me.__all)
        conn = Connection(path, id)
        session = Session(conn, [str(path.resolve())], me.__receiver)
        me.__thread.add(conn, session)
        me.__all.append((conn,session))
        return id

    def disconnect(me, id:int) -> None:
        session = me.__all[id][1]
        session.stop()
        me.__all[id] = None

    def n_connect(me) -> int: return len(me.__all)

    def connect_at(me, id:int) -> Connection:
        assert isinstance(me.__all[id], tuple)
        return me.__all[id][0]

    def connect_usable(me, id:int) -> bool:
        if id in range(len(me.__all)):
            if isinstance(me.__all[id],tuple):
                if me.__all[id][1].is_next():
                    return True
                else:
                    me.__all[id] = None
                    return False
        return False

    def send_connect(me, id:int, msg:str) -> None:
        assert isinstance(me.__all[id], tuple)
        session = me.__all[id][1]
        session.send(msg)

    def n_alive_connect(me): return me.__thread.n_usable()

    def send_alive_connects(me, msg:str) -> None:
        def f(_, session:Session) -> None:
            session.send(msg)
        me.__thread.for_items(f)
